//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.Activities
{
	#region Using Directives

	using System;
	using System.Collections.Generic;
	using System.Text;
	using System.ComponentModel;
	using System.Workflow.ComponentModel;
	using System.Collections;
	using System.Collections.Specialized;
	using System.Workflow.ComponentModel.Design;
	using System.ComponentModel.Design;
	using Microsoft.Research.ScientificWorkflow.NetCDF.CSharpAPI;
	using System.Workflow.ComponentModel.Compiler;
    using Microsoft.Research.ScientificWorkflow;
    using Microsoft.Research.ScientificWorkflow.Helpers;    
    using WorkflowData;
    using System.Drawing;
    using System.Drawing.Drawing2D;

	#endregion

	#region HyperCubeGeneratorActivity Class

    [Name("Hyper Cube Generator")]
    [Description("Generates Hyper Cube Object")]
    [WorkflowCategory("NetCdf")]
    [Designer(typeof(HyperCubeGeneratorDesigner), typeof(IDesigner))]
	public class HyperCubeGeneratorActivity : Activity
	{
		#region Private Fields

		private const string HyperCuberSchemaPropertyName = "HyperCubeSchemaGeneratorActivity";
		private NetCDFFile netCDFFile = null;
		private Dictionary<string, object[]> nonRecordValue = new Dictionary<string, object[]>();
		private Dictionary<uint, Dictionary<string, object[]>> recordValue = new Dictionary<uint, Dictionary<string, object[]>>();

        #endregion

		#region Dependency Properties

        public static DependencyProperty HyperCubeSchemaProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("HyperCubeSchema", typeof(HyperCubeSchema),
            typeof(HyperCubeGeneratorActivity));

        [RequiredInputParam]
        [Name("HyperCubeSchema Object")]
        [Description("HyperCube Schem Object for the Hypercube to be generated.")]
        public HyperCubeSchema HyperCubeSchema
        {
            get
            {
                return ((HyperCubeSchema)(base.GetValue(HyperCubeGeneratorActivity.HyperCubeSchemaProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.HyperCubeSchemaProperty, value);
            }
        }

        public static DependencyProperty XDimensionProperty =
            DependencyProperty.Register("XDimension", typeof(string), typeof(HyperCubeGeneratorActivity));

        [RequiredInputParam]
        [Name("XVariable Name", "X Dimension Name")]
        [Description("Name of the X Dimension")]
		public string XDimension
		{
			get
			{
                return ((string)(base.GetValue(HyperCubeGeneratorActivity.XDimensionProperty )));				
			}
			set
			{
                base.SetValue(HyperCubeGeneratorActivity.XDimensionProperty, value);
			}
		}


        public static DependencyProperty YDimensionProperty =
            DependencyProperty.Register("YDimension", typeof(string), typeof(HyperCubeGeneratorActivity));

        [RequiredInputParam]
        [Name("YVariable Name", "Y Dimension Name")]
        [Description("Name of the Y Dimension")]
		public string YDimension
		{
            get
            {
                return ((string)(base.GetValue(HyperCubeGeneratorActivity.YDimensionProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.YDimensionProperty, value);
            }
		}


        public static DependencyProperty ZDimensionProperty =
            DependencyProperty.Register("ZDimension", typeof(string), typeof(HyperCubeGeneratorActivity));

        [OptionalInputParam]
        [Name("ZVariable Name", "Z Dimension Name")]
        [Description("Name of the Z Dimension")]
		public string ZDimension
		{
            get
            {
                return ((string)(base.GetValue(HyperCubeGeneratorActivity.ZDimensionProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.ZDimensionProperty, value);
            }
        }


        public static DependencyProperty TDimensionProperty =
            DependencyProperty.Register("TDimension", typeof(string), typeof(HyperCubeGeneratorActivity));

        [RequiredInputParam]
        [Name("TVariable Name", "T Dimension Name")]
        [Description("Name of the T Dimension")]
		public string TDimension
		{
            get
            {
                return ((string)(base.GetValue(HyperCubeGeneratorActivity.TDimensionProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.TDimensionProperty, value);
            }
        }

        public static DependencyProperty CDFReaderProperty =
            DependencyProperty.Register("CDFReader", typeof(NetCDFReader), typeof(HyperCubeGeneratorActivity));

        [RequiredInputParam]
        [Name("NetCDF Reader Object")]
        [Description("netCDF reader object for reading data and writing to the HC.")]
        public NetCDFReader CDFReader
        {
            get
            {
                return ((NetCDFReader)(base.GetValue(HyperCubeGeneratorActivity.CDFReaderProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.CDFReaderProperty, value);
            }
        }

        public static DependencyProperty HyperCubeProperty =
            DependencyProperty.Register("HyperCube", typeof(HyperCube), typeof(HyperCubeGeneratorActivity));

        [OutputParam]
        [Name("HyperCube Object")]
        [Description("HyperCube object generated by this activity as output.")]
        public HyperCube HyperCube
        {
            get
            {
                return ((HyperCube)(base.GetValue(HyperCubeGeneratorActivity.HyperCubeProperty)));
            }
            set
            {
                base.SetValue(HyperCubeGeneratorActivity.HyperCubeProperty, value);
            }
        }

		#endregion

		#region Overridden Methods

		protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
		{
			#region Retrieve data
            this.netCDFFile = new NetCDFFile(this.CDFReader, null);

			NetCDFReader reader = this.netCDFFile.NetCDFMetadata;

            HyperCubeSchema schema = this.HyperCubeSchema; 
			HyperCube cube = new HyperCube(schema,
											GetAxisLength(reader,HyperCube.Axis.X), GetAxisLength(reader,HyperCube.Axis.Y),
											GetAxisLength(reader,HyperCube.Axis.Z), GetAxisLength( reader, HyperCube.Axis.T));
            
			#endregion

            #region Process Dimension Variables

            cube.XDimensionValues = GetDimensionValues(reader, schema.XDimensionName);
            cube.YDimensionValues = GetDimensionValues(reader, schema.YDimensionName);
            cube.ZDimensionValues = GetDimensionValues(reader, schema.ZDimensionName);
            cube.TDimensionValues = GetDimensionValues(reader, schema.TDimensionName);

            #endregion

            #region Process Non Record Data

			for (int i = 0; i < schema.ColumnCount; i++)
			{
				string varName = schema.GetColumnName(i);
				INetCDFVariable variable = reader.Variables[varName];

				if (!variable.IsRecordVariable)
				{
					int xAxisLength = 1;
					int yAxisLength = 1;
					int zAxisLength = 1;
					int tAxisLength = 1;

					for (uint dimIndex = 0; dimIndex < variable.DimensionIDs.Length; dimIndex++)
					{
						switch (cube.GetAxis(reader.Dimensions[variable.DimensionIDs[dimIndex]].Name))
						{
							case HyperCube.Axis.X:
								xAxisLength = GetAxisLength( reader, HyperCube.Axis.X);
								break;
							case HyperCube.Axis.Y:
								yAxisLength = GetAxisLength( reader, HyperCube.Axis.Y);
								break;
							case HyperCube.Axis.Z:
								zAxisLength = GetAxisLength( reader, HyperCube.Axis.Z);
								break;
							case HyperCube.Axis.T:
								tAxisLength = GetAxisLength( reader, HyperCube.Axis.T);
								break;
						}
					}
					InsertNonRecordVariableData(reader, variable, ref cube, xAxisLength, yAxisLength, zAxisLength, tAxisLength);
					nonRecordValue.Remove(variable.Name);
				}
			}
			#endregion

			#region Process Record Data

			for (uint recIndex = 0; recIndex < reader.NumberOfRecords; recIndex++)
			{
				for (int i = 0; i < schema.ColumnCount; i++)
				{
					string varName = schema.GetColumnName(i);
					INetCDFVariable variable = reader.Variables[varName];
					
                    if (variable.IsRecordVariable)
					{
						int xAxisLength = 1;
						int yAxisLength = 1;
						int zAxisLength = 1;
						for (uint dimIndex = 0; dimIndex < variable.DimensionIDs.Length; dimIndex++)
						{
							switch (cube.GetAxis(reader.Dimensions[variable.DimensionIDs[dimIndex]].Name))
							{
								case HyperCube.Axis.X:
									xAxisLength = GetAxisLength( reader, HyperCube.Axis.X);
									break;
								case HyperCube.Axis.Y:
									yAxisLength = GetAxisLength( reader, HyperCube.Axis.Y);
									break;
								case HyperCube.Axis.Z:
									zAxisLength = GetAxisLength( reader, HyperCube.Axis.Z);
									break;
							}

						}

						InsertRecordVariableData(reader, variable, ref cube, xAxisLength, yAxisLength, zAxisLength, recIndex);
					}
					try
					{
						recordValue[recIndex].Remove(variable.Name);
					}
					catch (KeyNotFoundException) 
					{
					}
				}
				recordValue.Remove(recIndex);
			}

			#endregion

			// Set the new hypercude as output property for this cube
            this.HyperCube = cube;
			return ActivityExecutionStatus.Closed;
		}

		#endregion

		#region Private Methods

        /// <summary>
        /// This method gets the values for the dimension variable passed
        /// </summary>
        /// <param name="reader">Reader object for reading the nc file</param>
        /// <param name="variableName">Name of the dimension varaible for which
        /// values are to read</param>
        /// <returns>List of values. Null if error or not found.</returns>
        private List<object> GetDimensionValues(NetCDFReader reader, string variableName)
        {
            if (string.IsNullOrEmpty(variableName))
                return null;

            INetCDFVariable variable = reader.Variables[variableName];
            if (null == variable)
                throw new ArgumentException("HyperCube Generator: Incorrect Dimension Name");
            
            // Call the method GetDataValue for either recrod or non record variable depending
            // on the flag. Call to this method would read all the values and store in the member 
            // dictionaries. The list can then be returned directly.
            List<object> outList = new List<object>();
            if (variable.IsRecordVariable)
            {
                for (int i = 0; i < reader.NumberOfRecords; ++i)
                {
                    if (null != GetDataValue(reader, variable, 0, (uint)i))
                    {
                        if (recordValue != null 
                            && recordValue.Count > 0 
                            && recordValue.ContainsKey((uint)i) 
                            && recordValue[(uint)i].ContainsKey(variableName))
                        {
                            outList.AddRange(recordValue[(uint)i][variableName]);
                        }
                    }
                }

                return outList;
            }
            else
            {
                if (null != GetDataValue(reader, variable, 0))
                {
                    if (nonRecordValue != null 
                        && nonRecordValue.Count > 0 
                        && nonRecordValue.ContainsKey(variableName))
                    {
                        outList.AddRange(nonRecordValue[variableName]);
                    }
                }
            }

            return outList;
        }

		private int GetAxisLength(NetCDFReader reader, HyperCube.Axis axis)
		{
			string dimName = String.Empty;
			switch (axis)
			{
				case HyperCube.Axis.X:
					{
						dimName = this.XDimension;
					}
					break;
				case HyperCube.Axis.Y:
					{
						dimName = this.YDimension;
					}
					break;
				case HyperCube.Axis.Z:
					{
						dimName = this.ZDimension;
					}
					break;
				case HyperCube.Axis.T:
					{
						dimName = this.TDimension;
					}
					break;
			}

			if (0 == dimName.Length)
			{
				// Axis not mapped
				return 1;
			}

			// Look for the variable in the file
			Dictionary<uint, INetCDFDimension>.Enumerator dimensionsEnumerator
				= this.netCDFFile.NetCDFMetadata.Dimensions.GetEnumerator();

			while (dimensionsEnumerator.MoveNext())
			{
				if (dimensionsEnumerator.Current.Value.Name == dimName)
				{
					if (!dimensionsEnumerator.Current.Value.IsRecordDimension)
					{
						return (int)dimensionsEnumerator.Current.Value.Length;
					}
					else
					{
						return (int)reader.NumberOfRecords;
					}
				}
			}
			// If exec comes here the dimension does not exist.
			throw new ArgumentException("Dimension " + dimName + " not found in file ");
		}

		private void InsertNonRecordVariableData(NetCDFReader reader, INetCDFVariable variable, ref HyperCube cube,
			int xAxis, int yAxis, int zAxis, int tAxis)
		{
			int colIndex = cube.Schema.LookupColumnIndex(variable.Name);
			int recCounter = 0;
			for (int x = 0; x < xAxis; x++)
			{
				for (int y = 0; y < yAxis; y++)
				{
					for (int z = 0; z < zAxis; z++)
					{
						for (int t = 0; t < tAxis; t++)
						{
							object data = GetDataValue(reader, variable, recCounter);

							if (null != data)
							{
								cube.SetData(data, x, y, z, t, colIndex);
							}
							recCounter++;
						}
					}
				}
			}
		}

		private void InsertRecordVariableData(NetCDFReader reader, INetCDFVariable variable, ref HyperCube cube,
			int xAxis, int yAxis, int zAxis, uint recIndex)
		{
			int colIndex = cube.Schema.LookupColumnIndex(variable.Name);
			int counter = 0;
			for (int x = 0; x < xAxis; x++)
			{
				for (int y = 0; y < yAxis; y++)
				{
					for (int z = 0; z < zAxis; z++)
					{

						object data = GetDataValue(reader, variable, counter, recIndex);

						if (null != data)
						{
							//Note : recIndex , As we are mapping T axis to Record Dimension
							cube.SetData(data, x, y, z, (int)recIndex, colIndex);
						}

						counter++;

					}
				}
			}
		}

		private object GetDataValue(NetCDFReader reader, INetCDFVariable myVariable, int indexVal)
		{
			object data = null;
			object[] dataArray = null;

			//Get the Data
			if (nonRecordValue.ContainsKey(myVariable.Name))
			{
				data = nonRecordValue[myVariable.Name][indexVal];
			}
			else
			{
				switch (myVariable.DataType)
				{
					case NetCDFDataType.NcFloat:
						float[] floatArray = GetValueOfNonRecordVariable<float>(reader, myVariable.Name);
						dataArray = new object[floatArray.Length];
						for (int f = 0; f < floatArray.Length; f++)
						{
							dataArray[f] = (object)floatArray[f];
						}
						//data = (object)floatArray[indexVal];
						break;
					case NetCDFDataType.NcShort:
						short[] shortArray = GetValueOfNonRecordVariable<short>(reader, myVariable.Name);
						dataArray = new object[shortArray.Length];
						for (int f = 0; f < shortArray.Length; f++)
						{
							dataArray[f] = (object)shortArray[f];
						}
						//data = (object)shortArray[indexVal];
						break;
					case NetCDFDataType.NcInt:
						int[] intArray = GetValueOfNonRecordVariable<int>(reader, myVariable.Name);
						dataArray = new object[intArray.Length];
						for (int f = 0; f < intArray.Length; f++)
						{
							dataArray[f] = (object)intArray[f];
						}
						//data = (object)intArray[indexVal];
						break;
					case NetCDFDataType.NcDouble:
						double[] doubleArray = GetValueOfNonRecordVariable<double>(reader, myVariable.Name);
						dataArray = new object[doubleArray.Length];
						for (int f = 0; f < doubleArray.Length; f++)
						{
							dataArray[f] = (object)doubleArray[f];
						}
						//data = (object)doubleArray[indexVal];
						break;
					case NetCDFDataType.NcChar:
						char[] charArray = GetValueOfNonRecordVariable<char>(reader, myVariable.Name);
						dataArray = new object[charArray.Length];
						for (int f = 0; f < charArray.Length; f++)
						{
							dataArray[f] = (object)charArray[f];
						}
						//data = (object)charArray[indexVal];
						break;
					case NetCDFDataType.NcByte:
						byte[] byteArray = GetValueOfNonRecordVariable<byte>(reader, myVariable.Name);
						dataArray = new object[byteArray.Length];
						for (int f = 0; f < byteArray.Length; f++)
						{
							dataArray[f] = (object)byteArray[f];
						}
						//data = (object)byteArray[indexVal];
						break;
				}

				//insert it to dictionary
				nonRecordValue.Add(myVariable.Name, dataArray);
				data = dataArray[indexVal];
			}

			return data;
		}

		private object GetDataValue(NetCDFReader reader, INetCDFVariable myVariable, int indexVal, uint recIndex)
		{
			object data = null;
			object[] dataArray = null;

			//Get the Data

			if (recordValue.ContainsKey(recIndex) && recordValue[recIndex].ContainsKey(myVariable.Name))
			{
				data = recordValue[recIndex][myVariable.Name][indexVal];
			}
			else
			{

				//Get the record and add it to dictionary
				#region Get Record
				switch (myVariable.DataType)
				{
					case NetCDFDataType.NcFloat:
						float[] floatArray = GetValueOfRecordVariable<float>(reader, myVariable.Name, recIndex);
						dataArray = new object[floatArray.Length];
						for (int f = 0; f < floatArray.Length; f++)
						{
							dataArray[f] = (object)floatArray[f];
						}
						//data = (object)floatArray[indexVal];
						break;
					case NetCDFDataType.NcShort:
						short[] shortArray = GetValueOfRecordVariable<short>(reader, myVariable.Name, recIndex);
						dataArray = new object[shortArray.Length];
						for (int f = 0; f < shortArray.Length; f++)
						{
							dataArray[f] = (object)shortArray[f];
						}
						//data = (object)shortArray[indexVal];
						break;
					case NetCDFDataType.NcInt:
						int[] intArray = GetValueOfRecordVariable<int>(reader, myVariable.Name, recIndex);
						dataArray = new object[intArray.Length];
						for (int f = 0; f < intArray.Length; f++)
						{
							dataArray[f] = (object)intArray[f];
						}
						//data = (object)intArray[indexVal];
						break;
					case NetCDFDataType.NcDouble:
						double[] doubleArray = GetValueOfRecordVariable<double>(reader, myVariable.Name, recIndex);
						dataArray = new object[doubleArray.Length];
						for (int f = 0; f < doubleArray.Length; f++)
						{
							dataArray[f] = (object)doubleArray[f];
						}
						//data = (object)doubleArray[indexVal];
						break;
					case NetCDFDataType.NcChar:
						char[] charArray = GetValueOfRecordVariable<char>(reader, myVariable.Name, recIndex);
						dataArray = new object[charArray.Length];
						for (int f = 0; f < charArray.Length; f++)
						{
							dataArray[f] = (object)charArray[f];
						}
						//data = (object)charArray[indexVal];
						break;
					case NetCDFDataType.NcByte:
						byte[] byteArray = GetValueOfRecordVariable<byte>(reader, myVariable.Name, recIndex);
						dataArray = new object[byteArray.Length];
						for (int f = 0; f < byteArray.Length; f++)
						{
							dataArray[f] = (object)byteArray[f];
						}
						//data = (object)byteArray[indexVal];
						break;
				}
				#endregion

				if (recordValue.ContainsKey(recIndex))
				{
					recordValue[recIndex].Add(myVariable.Name, dataArray);
				}
				else
				{
					Dictionary<string, object[]> varDataDictionary = new Dictionary<string, object[]>();
					varDataDictionary.Add(myVariable.Name, dataArray);
					recordValue.Add(recIndex, varDataDictionary);
				}
				data = dataArray[indexVal];
			}
			// data = recordValue[myVariable.Name][indexVal];

			return data;
		}

		private T[] GetValueOfRecordVariable<T>(NetCDFReader reader, string name, uint record_index)
		{
			T[] var = reader.readVariable<T>(name, record_index);
			return var;
		}

		private T[] GetValueOfNonRecordVariable<T>(NetCDFReader reader, string name)
		{
			T[] var = reader.readVariable<T>(name);
			return var;
		}

		/// <summary>
		/// This method checks if the mapping var specified exits. If yes then
		/// returns the axis count.
		/// If not throws an ArgumentException
		/// </summary>
		private int VerifyAxisMapping(HyperCube.Axis axis, ref NcFile file)
		{
			string dimName = "";
			switch (axis)
			{
				case HyperCube.Axis.X:
					{
						dimName = this.XDimension;
					}
					break;
				case HyperCube.Axis.Y:
					{
						dimName = this.YDimension;
					}
					break;
				case HyperCube.Axis.Z:
					{
						dimName = this.ZDimension;
					}
					break;
				case HyperCube.Axis.T:
					{
						dimName = this.TDimension;
					}
					break;
			}

			if (0 == dimName.Length)
			{
				// Axis not mapped
				return 1;
			}

			// Look for the variable in the file
			foreach (NcDim dim in file.MetaData.Dimensions)
			{
				if (dim.Name == dimName)
				{
					return (dim.IsUnlimited ? 1 : dim.Size);
				}
			}

			// If exec comes here the dimension does not exist.
			throw new ArgumentException("Dimension " + dimName + " not found in file ");
		}

		#endregion

        #region HyperCubeSchemaGeneratorDesigner

        [ActivityDesignerTheme(typeof(HyperCubeSchemaGeneratorDesignerTheme))]
        public class HyperCubeGeneratorDesigner : ActivityDesigner
        {
            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
            }
        }

        #endregion

        #region HyperCubeGeneratorDesignerTheme

        public class HyperCubeGeneratorDesignerTheme : ActivityDesignerTheme
        {
            public HyperCubeGeneratorDesignerTheme(WorkflowTheme theme)
                : base(theme)
            {
                this.ForeColor = Color.FromArgb(0xff, 0, 0, 0);
                this.BorderColor = Color.FromArgb(0xff, 0x79, 0x73, 0xa5);
                this.BorderStyle = DashStyle.Solid;
                this.BackColorStart = Color.FromArgb(0xff, 0xff, 0xe8, 0xdf);
                this.BackColorEnd = Color.FromArgb(0xff, 0xff, 0xb3, 0x95);
                this.BackgroundStyle = LinearGradientMode.Horizontal;
            }

        }

        #endregion

        //#region IPropertyValueProvider Members

        //public ICollection GetPropertyValues(ITypeDescriptorContext typeDescriptorContext)
        //{
        //    StringCollection propertyCollection = new StringCollection();
        //    if (typeDescriptorContext.PropertyDescriptor.Name == HyperCuberSchemaPropertyName)
        //    {
        //        propertyCollection =
        //            ActivityHelper.GetAllActivityNames(this, typeof(HyperCubeSchemaGeneratorActivity));
        //    }

        //    return propertyCollection;
        //}

        //#endregion

	}

	#endregion
}
